#pragma once 

#include <vector>
#include <algorithm>
#include <hash_map>

#include <boost/tokenizer.hpp>
#include <boost/lexical_cast/lexical_cast_old.hpp>

#include "../Neighbors/Neighbour.h"
#include "../../UtilityClasses/HelperClass.h"
#include "../../UtilityClasses/Common.h"
#include "../../../../RayTracing/CoreClasses/UtilClasses/Common.h"

using std::vector;
using std::hash_map;
namespace Network
{
	namespace Peer
	{

		///-------------------------------------------------------------------------------------------------
		/// <summary>	Defines an alias representing the tokenizer. </summary>
		///
		/// <remarks>	Adrian, 31/08/2015. </remarks>
		///-------------------------------------------------------------------------------------------------

		typedef boost::tokenizer<boost::char_separator<char>> tokenizer;

		const int CACHE_SIZE_LIMIT = 5;

		class NeighbourManager
		{
		public:


			NeighbourManager()
			{
				m_internalDescription = new Neighbour("127.0.0.1", "20000");
			}


			///-------------------------------------------------------------------------------------------------
			/// <summary>	Constructor. </summary>
			///
			/// <remarks>	Adrian, 11/09/2015. </remarks>
			///
			/// <param name="p_peerGuid"> 	Unique identifier for the peer. </param>
			/// <param name="p_myAddress">	my address. </param>
			///-------------------------------------------------------------------------------------------------

			NeighbourManager(Neighbour* p_internalRepresentation)
			{
				m_internalDescription = p_internalRepresentation;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets random neighbour. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <returns>	The random neighbour. </returns>
			///-------------------------------------------------------------------------------------------------

			Neighbour* GetRandomNeighbour()
			{
				if (m_neighbours.size() == 0)
				{
					return nullptr;
				}
				else
				{
					int randInteger = rand() % m_neighbours.size();

					int counter = 0;

					for (auto const &iter : m_neighbours)
					{
						if (counter == randInteger)
						{
							Neighbour* current = iter.second;
							current->IncrementLogicalTime();
							return current;
						}
						else
							++counter;
					}
				}

				return nullptr;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Removes the neighbour described by neighboursAddress. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="neighboursAddress">	The neighbours address. </param>
			///-------------------------------------------------------------------------------------------------

			void RemoveNeighbour(std::string neighboursAddress)
			{
				int peerIndex = 0;
				bool result = FindNeighbour(neighboursAddress.c_str());

				if (result)
				{
					//TODO Fix here
				}
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets a neighbour. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="index">	Zero-based index of the. </param>
			///
			/// <returns>	The neighbour. </returns>
			///-------------------------------------------------------------------------------------------------

			Neighbour* GetNeighbour(int index)
			{
				int counter = 0;
				if (index < m_neighbours.size() || index >= 0)
				{
					for (auto const &iter : m_neighbours)
					{
						if (counter == index)
						{
							Neighbour* current = iter.second;
							return current;
				}
						else
							++counter;
					}

				}

				return nullptr;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets a neighbour. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="address">	[in,out] The address. </param>
			///
			/// <returns>	The neighbour. </returns>
			///-------------------------------------------------------------------------------------------------

			Neighbour* GetNeighbour(string p_nieghborSignitaure)
			{
				int index = 0;
				if (FindNeighbour(p_nieghborSignitaure))
					return m_neighbours[p_nieghborSignitaure];
				else
					return nullptr;
				
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Sort cache. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///-------------------------------------------------------------------------------------------------
			void SortCache()
			{
				//std::sort(m_neighbours.begin(), m_neighbours.end());
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Parse neighbours. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <returns>	A std::string. </returns>
			///-------------------------------------------------------------------------------------------------
			std::string ParseNeighbours()
			{
				std::stringstream output;
				for (auto const &iter : m_neighbours)
				{
					Neighbour* current = iter.second;
					output << current->ToString();
					output << ";";
				}
				return output.str();
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Adds a new neighbours. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="stringNeighbours">	[in,out] The string neighbours. </param>
			///-------------------------------------------------------------------------------------------------

			void AddNewNeighbours(std::string stringNeighbours)
			{
				boost::char_separator<char> sep(";");
				vector<string> parsedTokens;

				HelperClass::ParseStringSentence(stringNeighbours, ";", parsedTokens);

				vector<Neighbour*> neighborsList;
				for (auto const &iter : m_neighbours)
				{
					neighborsList.push_back(iter.second);
				}

				for (int index = 0; index < parsedTokens.size(); index++)
				{
					Neighbour* temp_neighbour = new Neighbour(parsedTokens[index]);
					if ((temp_neighbour->GetNeigbourRepresentation()) != this->m_internalDescription->GetNeigbourRepresentation())
						neighborsList.push_back(temp_neighbour);
				}

				std::sort(neighborsList.begin(), neighborsList.end(), SortFunction);

				if (neighborsList.size() > CACHE_SIZE_LIMIT)
				{
					CleanHashMap();
				}

				for (int neighborIndex = 0; neighborIndex < neighborsList.size(); neighborIndex++)
				{
					if (neighborIndex <= CACHE_SIZE_LIMIT)
					{
						m_neighbours[neighborsList[neighborIndex]->GetNeigbourRepresentation()] = neighborsList[neighborIndex];
					}
					else
					{
						Neighbour* current = neighborsList[neighborIndex];
						neighborsList.erase(neighborsList.begin() + neighborIndex);
						delete current;
					}
				}
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets the neighbours. </summary>
			///
			/// <remarks>	Adrian, 11/09/2015. </remarks>
			///
			/// <param name="p_stringNeighbors">	The string neighbors. </param>
			/// <param name="p_neighborsList">  	[in,out] [in,out] If non-null, list of neighbors. </param>
			///-------------------------------------------------------------------------------------------------

			void GetNeighbours(string p_stringNeighbors, vector<Neighbour*>& p_neighborsList)
			{
				boost::char_separator<char> sep(";");
				std::string output = std::string(p_stringNeighbors);
				tokenizer tokens(output, sep);

				for (tokenizer::iterator iter = tokens.begin(); iter != tokens.end(); iter++)
				{
					p_neighborsList.push_back(ProduceNeighbor(*iter));
				}
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Sets the list to the top most peers  </summary>
			///
			/// <remarks>	Adrian, 05/09/2015. </remarks>
			///
			/// <param name="p_recievedPeerList">	List of recieved peers. </param>
			///-------------------------------------------------------------------------------------------------

			void GetTopNeighbors(string p_recievedPeerList)
			{
				vector<Neighbour*> allNeighbors;
				GetNeighbours(p_recievedPeerList, allNeighbors);

				for (auto const &iter : m_neighbours)
				{
					Neighbour* current = iter.second;
					allNeighbors.push_back(current);
				}

				//std::sort(allNeighbors.begin(), allNeighbors.end());

				m_neighbours.empty();
				int counter = allNeighbors.size() - 1;
				for (int index = 0; index < CACHE_SIZE_LIMIT; index++)
				{
					Neighbour* currentNeighbor = allNeighbors[counter];
					m_neighbours[currentNeighbor->GetNeigbourRepresentation()] = currentNeighbor;
				}
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Produce neighbor. </summary>
			///
			/// <remarks>	Adrian, 05/09/2015. </remarks>
			///
			/// <param name="currentNeighbor">	The current neighbor. </param>
			///
			/// <returns>	null if it fails, else a Neighbour*. </returns>
			///-------------------------------------------------------------------------------------------------
			Neighbour* ProduceNeighbor(std::string currentNeighbor)
			{
				std::string myArray[2];
				HelperClass::ParseStringSentence(currentNeighbor, "-", myArray);
				return new Neighbour(myArray[1].c_str(), myArray[0].c_str(), (int)stoi(myArray[2].c_str()));
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Adds a neighbor to 'address'. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="peerName">	[in,out] Name of the peer. </param>
			/// <param name="address"> 	[in,out] The address. </param>
			///-------------------------------------------------------------------------------------------------

			void AddNeighbor(char* p_ipAddress, char* p_port)
			{
				cout << "adding new neighbor" << p_ipAddress<< ":::" << p_port << endl;
				int testInt = 0;
				string  toSearch = string(p_ipAddress) + ":" + p_port;
				if (!FindNeighbour(toSearch))
				{
					if (m_neighbours.size() >= MAXIMUM_AMOUNT_NEIGHBORS)
					{
						SortCache();
						RemoveNeighbour(toSearch);
					}

					m_neighbours[toSearch] =  new Neighbour(p_ipAddress, p_port);
				}
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Searches for the first neighbour. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <param name="address">	[in,out] The address. </param>
			/// <param name="index">  	[in,out] Zero-based index of the. </param>
			///
			/// <returns>	true if it succeeds, false if it fails. </returns>
			///-------------------------------------------------------------------------------------------------

			bool FindNeighbour(string p_neighbourRepresentation)
			{
				if (m_neighbours.find(p_neighbourRepresentation) == m_neighbours.end())
					return false;
				else
					return true;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Updates the neighbours cache described by p_recievedPeers. </summary>
			///
			/// <remarks>	Adrian, 10/09/2015. </remarks>
			///
			/// <param name="p_recievedPeers">	The recieved peers. </param>
			///-------------------------------------------------------------------------------------------------

			void UpdateNeighboursCache(string p_recievedPeers)
			{
				GetTopNeighbors(p_recievedPeers);
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets random neighbors. </summary>
			///
			/// <remarks>	Adrian, 31/08/2015. </remarks>
			///
			/// <returns>	The random neighbors. </returns>
			///-------------------------------------------------------------------------------------------------
			std::string GetRandomNeighbors()
			{
				int amountOfNeighbours = m_neighbours.size();
				std::string output = "";

				for (auto const &iter: m_neighbours)
				{
					Neighbour* current = iter.second;
					output = output + current->ToString() + ";";
				}
				
				return output;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Sort function descending order on last connection </summary>
			///
			/// <remarks>	Adrian, 11/09/2015. </remarks>
			///
			/// <param name="p_lhs">	[in,out] If non-null, the left hand side. </param>
			/// <param name="p_rhs">	[in,out] If non-null, the right hand side. </param>
			///
			/// <returns>	true if it succeeds, false if it fails. </returns>
			///-------------------------------------------------------------------------------------------------

			static bool SortFunction(const Neighbour* p_lhs, const Neighbour* p_rhs) 
			{
				return p_lhs->lastConnection < p_rhs->lastConnection;
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Clean hash map. </summary>
			///
			/// <remarks>	Adrian, 11/09/2015. </remarks>
			///-------------------------------------------------------------------------------------------------

			void CleanHashMap()
			{
				auto const &iter = m_neighbours.begin();
				std::next(iter, MAXIMUM_AMOUNT_NEIGHBORS);
				m_neighbours.erase(iter, m_neighbours.end());
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Gets amount peers. </summary>
			///
			/// <remarks>	Adrian, 09/09/2015. </remarks>
			///
			/// <returns>	The amount peers. </returns>
			///-------------------------------------------------------------------------------------------------

			int GetAmountPeers()
			{
				return this->m_neighbours.size();
			}

			void SetInternalIp(string ipAddress)
			{
				m_internalDescription->m_Ip = new char[ipAddress.size()];
				std::strcpy(m_internalDescription->m_Ip, ipAddress.c_str());
			}

			void UpdateNeighborTime(string p_IpAddress, string p_Port)
			{
				string idString = p_IpAddress + ":" + p_Port;

				Neighbour* neighborToUpdate = GetNeighbour(idString);

				if (neighborToUpdate != nullptr)
					neighborToUpdate->IncrementLogicalTime();
			}

			///-------------------------------------------------------------------------------------------------
			/// <summary>	Destructor. </summary>
			///
			/// <remarks>	Adrian, 09/09/2015. </remarks>
			///-------------------------------------------------------------------------------------------------

			~NeighbourManager()
			{
				/*Engine::Common::EmptyPointerVector(m_neighbours);*/
			}



		private:
			hash_map<string, Neighbour*> m_neighbours;
			Neighbour* m_internalDescription;
		};
	}
}